This notebook is part of PyBroMo a python-based single-molecule Brownian motion diffusion simulator that simulates confocal smFRET experiments.
In this notebook we show how to perform a 3-D trajectories simulation of a set of freely diffusing molecules. The simulation computes (and saves!) 3-D trajectories and emission rates due to a confocal excitation PSF for each single molecule. Depending on the simulation length, the required disk space can be significant (~ 750MB per minute of simulated diffusion).
For more info see PyBroMo Homepage.
Together with a few standard python libraries we import PyBroMo using the short name pbm
.
All PyBroMo functions will be available as pbm.
something.
In [ ]:
%matplotlib inline
import numpy as np
import tables
import matplotlib.pyplot as plt
import seaborn as sns
import pybromo as pbm
print('Numpy version:', np.__version__)
print('PyTables version:', tables.__version__)
print('PyBroMo version:', pbm.__version__)
Then we define the simulation parameters:
In [ ]:
# Initialize the random state
rs = np.random.RandomState(seed=1)
print('Initial random state:', pbm.hash_(rs.get_state()))
# Diffusion coefficient
Du = 12.0 # um^2 / s
D1 = Du*(1e-6)**2 # m^2 / s
D2 = D1/2
# Simulation box definition
box = pbm.Box(x1=-4.e-6, x2=4.e-6, y1=-4.e-6, y2=4.e-6, z1=-6e-6, z2=6e-6)
# PSF definition
psf = pbm.NumericPSF()
# Particles definition
P = pbm.Particles(num_particles=20, D=D1, box=box, rs=rs)
P.add(num_particles=15, D=D2)
# Simulation time step (seconds)
t_step = 0.5e-6
# Time duration of the simulation (seconds)
t_max = 1
# Particle simulation definition
S = pbm.ParticlesSimulation(t_step=t_step, t_max=t_max,
particles=P, box=box, psf=psf)
print('Current random state:', pbm.hash_(rs.get_state()))
The most important line is the last line which creates an object S
that contains all the simulation parameters (it also contains methods to run
the simulation). You can print S
and check the current parameters:
In [ ]:
S
Other useful simulation info:
In [ ]:
S.compact_name()
In [ ]:
S.particles.diffusion_coeff_counts
Arrays sizes:
In [ ]:
S.print_sizes()
NOTE: This is the maximum in-memory array size when using a single chunk. In the following, we simulate the diffusion in smaller time windows (chunks), thus requiring only a few tens MB of RAM, regardless of the simulated duration.
In the brownian motion simulation we keep using the same random state object rs
.
Initial and final state are saved so the same simulation can be reproduced.
See PyBroMo - A1. Reference - Data format and internals.ipynb
for more info on the random state.
In [ ]:
print('Current random state:', pbm.hash_(rs.get_state()))
In [ ]:
S.simulate_diffusion(total_emission=False, save_pos=True, verbose=True, rs=rs, chunksize=2**19, chunkslice='times')
In [ ]:
print('Current random state:', pbm.hash_(rs.get_state()))
The normalized emission rate (peaks at 1) for each particle is stored
in a 2D pytable array and accessible through the emission
attribute:
In [ ]:
print('Simulation file size: %d MB' % (S.store.h5file.get_filesize()/1024./1024.))
In [ ]:
S.store.close()
In [ ]:
S = pbm.ParticlesSimulation.from_datafile('0168') # Read-only by default
In [ ]:
S
This are basic debug plots. For more advanged interactive exploration of trajectory and emission arrays see the next notebook:
In [ ]:
def plot_emission(S, s=0, size=2e6, slice_=None, em_th=0.01, save=False, figsize=(9, 4.5)):
if slice_ is None:
slice_ = (s*size, (s+1)*size)
slice_ = slice(*slice_)
em = S.emission[:, slice_]
dec = 1 if slice_.step is None else slice_.step
t_step = S.t_step*dec
t = np.arange(em.shape[1])*(t_step*1e3)
fig, ax = plt.subplots(figsize=figsize)
for ip, em_ip in enumerate(em):
if em_ip.max() < em_th: continue
plt.plot(t, em_ip, label='P%d' % ip)
ax.set_xlabel('Time (ms)')
rs_hash = pbm.hash_(S.traj_group._v_attrs['init_random_state'])[:3]
ax.set_title('%ds ID-EID: %d-%d, sim rs = %s, part rs = %s' %\
(s, S.ID, S.EID, rs_hash, S.particles.rs_hash[:3]))
ax.legend(bbox_to_anchor=(1.03, 1), loc=2, borderaxespad=0.)
if save:
plt.savefig('em %ds ID-EID %d-%d, rs=%s' %\
(s, S.ID, S.EID, rs_hash),
dpi=200, bbox_inches='tight')
#plt.close(fig)
#display(fig)
#fig.clear()
In [ ]:
plot_emission(S, slice_=(0, 2e6, 10), em_th=0.05)
In [ ]:
def plot_tracks(S, slice_=None, particles=None):
if slice_ is None:
slice_ = (0, 100e3, 100)
duration = (slice_[1] - slice_[0])*S.t_step
slice_ = slice(*slice_)
if particles is None:
particles = range(S.num_particles)
fig, AX = plt.subplots(1, 2, figsize=(11, 5), sharey=True)
plt.subplots_adjust(left=0.05, right=0.93, top=0.95, bottom=0.09,
wspace=0.05)
plt.suptitle("Total: %.1f s, Visualized: %.2f ms" % (
S.t_step*S.n_samples, duration*1e3))
for ip in particles:
x, y, z = S.position[ip, :, slice_]
x0, y0, z0 = S.particles[ip].r0
plot_kwargs = dict(ls='', marker='o', mew=0, ms=2, alpha=0.5,
label='P%d' % ip)
l, = AX[0].plot(x*1e6, y*1e6, **plot_kwargs)
AX[1].plot(z*1e6, y*1e6, color=l.get_color(), **plot_kwargs)
#AX[1].plot([x0*1e6], [y0*1e6], 'o', color=l.get_color())
#AX[0].plot([x0*1e6], [z0*1e6], 'o', color=l.get_color())
AX[0].set_xlabel("x (um)")
AX[0].set_ylabel("y (um)")
AX[1].set_xlabel("z (um)")
sig = np.array([0.2, 0.2, 0.6])*1e-6
## Draw an outline of the PSF
a = np.arange(360)/360.*2*np.pi
rx, ry, rz = (sig) # draw radius at 3 sigma
AX[0].plot((rx*np.cos(a))*1e6, (ry*np.sin(a))*1e6, lw=2, color='k')
AX[1].plot((rz*np.cos(a))*1e6, (ry*np.sin(a))*1e6, lw=2, color='k')
AX[0].set_xlim(-4, 4)
AX[0].set_ylim(-4, 4)
AX[1].set_xlim(-4, 4)
if len(particles) <= 20:
AX[1].legend(bbox_to_anchor=(1.01, 1), loc=2, borderaxespad=0.)
In [ ]:
plot_tracks(S) #particles=[2, 5, 7, 22, 30])
In [ ]:
S.store.close()
Here we simulate some timestamps array one by one. To generate smFRET data in one step (donor + acceptor, single or multiple populations and create Photon-HDF5 files see the next notebook:
In [ ]:
S = pbm.ParticlesSimulation.from_datafile('0168', mode='w')
Simulate timestamps for a single population comprised of all the 35 particles in the diffusion simulation:
In [ ]:
S.simulate_timestamps_mix(max_rates=(200e3,),
populations=(slice(0, 35),),
bg_rate=1e3)
Simulate timestamps for a two population (respectively 20 and 15 particles) with different maximum emission rates:
In [ ]:
S.simulate_timestamps_mix(max_rates=(250e3, 180e3),
populations=(slice(0,20), slice(20, 35)),
bg_rate=1.2e3)
In [ ]:
S.timestamp_names
Get the timestamps and particles (pytables) arrays:
In [ ]:
ts, part = S.get_timestamps_part('Pop1_P35_Pstart0_max_rate200000cps_BG1000cps_t_1s_rs_bfb867')
Slice to get the data:
In [ ]:
ts[:]
You can find the name of a timestamps array with:
In [ ]:
S.timestamps_match_mix(max_rates=(200e3,), populations=(slice(0, 35),), bg_rate=1e3)
In [ ]: